/*************************************************************************************************
 * The command line utility of the table database API
 *                                                               Copyright (C) 2006-2012 FAL Labs
 * This file is part of Tokyo Cabinet.
 * Tokyo Cabinet is free software; you can redistribute it and/or modify it under the terms of
 * the GNU Lesser General Public License as published by the Free Software Foundation; either
 * version 2.1 of the License or any later version.  Tokyo Cabinet is distributed in the hope
 * that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 * License for more details.
 * You should have received a copy of the GNU Lesser General Public License along with Tokyo
 * Cabinet; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307 USA.
 *************************************************************************************************/


#include <tcutil.h>
#include <tctdb.h>
#include "myconf.h"


/* global variables */
const char *g_progname; // program name
HANDLE g_dbgfd = INVALID_HANDLE_VALUE; // debugging output


/* function prototypes */
int main(int argc, char **argv);
static void usage(void);
static void printerr(TCTDB *tdb);
static int printdata(const char *ptr, int size, bool px);
static char *mygetline(FILE *ifp);
static int runcreate(int argc, char **argv);
static int runinform(int argc, char **argv);
static int runput(int argc, char **argv);
static int runout(int argc, char **argv);
static int runget(int argc, char **argv);
static int runlist(int argc, char **argv);
static int runsearch(int argc, char **argv);
static int runoptimize(int argc, char **argv);
static int runsetindex(int argc, char **argv);
static int runimporttsv(int argc, char **argv);
static int runversion(int argc, char **argv);
static int proccreate(const char *path, int bnum, int apow, int fpow, int opts);
static int procinform(const char *path, int omode);
static int procput(const char *path, const char *pkbuf, int pksiz, TCMAP *cols,
        int omode, int dmode);
static int procout(const char *path, const char *pkbuf, int pksiz, int omode);
static int procget(const char *path, const char *pkbuf, int pksiz, int omode, bool px, bool pz);
static int proclist(const char *path, int omode, int max, bool pv, bool px, const char *fmstr);
static int procsearch(const char *path, TCLIST *conds, const char *oname, const char *otype,
        int omode, int max, int skip, bool pv, bool px, bool kw, bool ph, int bt,
        bool rm, const char *mtype);
static int procoptimize(const char *path, int bnum, int apow, int fpow, int opts, int omode,
        bool df);
static int procsetindex(const char *path, const char *name, int omode, int type);
static int procimporttsv(const char *path, const char *file, int omode, bool sc);
static int procversion(void);

/* main routine */
int main(int argc, char **argv) {
    g_progname = argv[0];
    g_dbgfd = INVALID_HANDLE_VALUE;
    const char *ebuf = getenv("TCDBGFD");
    if (ebuf) {
        int debugfd = tcatoix(ebuf);
#ifdef _WIN32
        g_dbgfd = (HANDLE) _get_osfhandle(debugfd);
#else
        g_dbgfd = debugfd;
#endif
    }
    if (argc < 2) usage();
    int rv = 0;
    if (!strcmp(argv[1], "create")) {
        rv = runcreate(argc, argv);
    } else if (!strcmp(argv[1], "inform")) {
        rv = runinform(argc, argv);
    } else if (!strcmp(argv[1], "put")) {
        rv = runput(argc, argv);
    } else if (!strcmp(argv[1], "out")) {
        rv = runout(argc, argv);
    } else if (!strcmp(argv[1], "get")) {
        rv = runget(argc, argv);
    } else if (!strcmp(argv[1], "list")) {
        rv = runlist(argc, argv);
    } else if (!strcmp(argv[1], "search")) {
        rv = runsearch(argc, argv);
    } else if (!strcmp(argv[1], "optimize")) {
        rv = runoptimize(argc, argv);
    } else if (!strcmp(argv[1], "setindex")) {
        rv = runsetindex(argc, argv);
    } else if (!strcmp(argv[1], "importtsv")) {
        rv = runimporttsv(argc, argv);
    } else if (!strcmp(argv[1], "version") || !strcmp(argv[1], "--version")) {
        rv = runversion(argc, argv);
    } else {
        usage();
    }
    return rv;
}

/* print the usage and exit */
static void usage(void) {
    fprintf(stderr, "%s: the command line utility of the table database API\n", g_progname);
    fprintf(stderr, "\n");
    fprintf(stderr, "usage:\n");
    fprintf(stderr, "  %s create [-tl] [-td|-tb|-tt|-tx] path [bnum [apow [fpow]]]\n", g_progname);
    fprintf(stderr, "  %s inform [-nl|-nb] path\n", g_progname);
    fprintf(stderr, "  %s put [-nl|-nb] [-sx] [-dk|-dc|-dai|-dad] path pkey [cols...]\n",
            g_progname);
    fprintf(stderr, "  %s out [-nl|-nb] [-sx] path pkey\n", g_progname);
    fprintf(stderr, "  %s get [-nl|-nb] [-sx] [-px] [-pz] path pkey\n", g_progname);
    fprintf(stderr, "  %s list [-nl|-nb] [-m num] [-pv] [-px] [-fm str] path\n", g_progname);
    fprintf(stderr, "  %s search [-nl|-nb] [-ord name type] [-m num] [-sk num] [-kw] [-pv] [-px]"
            " [-ph] [-bt num] [-rm] [-ms type] path [name op expr ...]\n", g_progname);
    fprintf(stderr, "  %s optimize [-tl] [-td|-tb|-tt|-tx] [-tz] [-nl|-nb] [-df]"
            " path [bnum [apow [fpow]]]\n", g_progname);
    fprintf(stderr, "  %s setindex [-nl|-nb] [-it type] path name\n", g_progname);
    fprintf(stderr, "  %s importtsv [-nl|-nb] [-sc] path [file]\n", g_progname);
    fprintf(stderr, "  %s version\n", g_progname);
    fprintf(stderr, "\n");
    exit(1);
}

/* print error information */
static void printerr(TCTDB *tdb) {
    const char *path = tctdbpath(tdb);
    int ecode = tctdbecode(tdb);
    fprintf(stderr, "%s: %s: %d: %s\n", g_progname, path ? path : "-", ecode, tctdberrmsg(ecode));
}

/* print record data */
static int printdata(const char *ptr, int size, bool px) {
    int len = 0;
    while (size-- > 0) {
        if (px) {
            if (len > 0) putchar(' ');
            len += printf("%02X", *(unsigned char *) ptr);
        } else {
            if (strchr("\t\r\n", *ptr)) {
                putchar(' ');
            } else {
                putchar(*ptr);
            }
            len++;
        }
        ptr++;
    }
    return len;
}

/* read a line from a file descriptor */
static char *mygetline(FILE *ifp) {
    int len = 0;
    int blen = 1024;
    char *buf = tcmalloc(blen + 1);
    bool end = true;
    int c;
    while ((c = fgetc(ifp)) != EOF) {
        end = false;
        if (c == '\0') continue;
        if (blen <= len) {
            blen *= 2;
            buf = tcrealloc(buf, blen + 1);
        }
        if (c == '\n' || c == '\r') c = '\0';
        buf[len++] = c;
        if (c == '\0') break;
    }
    if (end) {
        tcfree(buf);
        return NULL;
    }
    buf[len] = '\0';
    return buf;
}

/* parse arguments of create command */
static int runcreate(int argc, char **argv) {
    char *path = NULL;
    char *bstr = NULL;
    char *astr = NULL;
    char *fstr = NULL;
    int opts = 0;
    for (int i = 2; i < argc; i++) {
        if (!path && argv[i][0] == '-') {
            if (!strcmp(argv[i], "-tl")) {
                opts |= TDBTLARGE;
            } else if (!strcmp(argv[i], "-td")) {
                opts |= TDBTDEFLATE;
            } else if (!strcmp(argv[i], "-tb")) {
                opts |= TDBTBZIP;
            } else if (!strcmp(argv[i], "-tt")) {
                opts |= TDBTTCBS;
            } else if (!strcmp(argv[i], "-tx")) {
                opts |= TDBTEXCODEC;
            } else {
                usage();
            }
        } else if (!path) {
            path = argv[i];
        } else if (!bstr) {
            bstr = argv[i];
        } else if (!astr) {
            astr = argv[i];
        } else if (!fstr) {
            fstr = argv[i];
        } else {
            usage();
        }
    }
    if (!path) usage();
    int bnum = bstr ? tcatoix(bstr) : -1;
    int apow = astr ? tcatoix(astr) : -1;
    int fpow = fstr ? tcatoix(fstr) : -1;
    int rv = proccreate(path, bnum, apow, fpow, opts);
    return rv;
}

/* parse arguments of inform command */
static int runinform(int argc, char **argv) {
    char *path = NULL;
    int omode = 0;
    for (int i = 2; i < argc; i++) {
        if (!path && argv[i][0] == '-') {
            if (!strcmp(argv[i], "-nl")) {
                omode |= TDBONOLCK;
            } else if (!strcmp(argv[i], "-nb")) {
                omode |= TDBOLCKNB;
            } else {
                usage();
            }
        } else if (!path) {
            path = argv[i];
        } else {
            usage();
        }
    }
    if (!path) usage();
    int rv = procinform(path, omode);
    return rv;
}

/* parse arguments of put command */
static int runput(int argc, char **argv) {
    char *path = NULL;
    char *pkey = NULL;
    TCLIST *vals = tcmpoollistnew(tcmpoolglobal());
    int omode = 0;
    int dmode = 0;
    bool sx = false;
    for (int i = 2; i < argc; i++) {
        if (!path && argv[i][0] == '-') {
            if (!strcmp(argv[i], "-nl")) {
                omode |= TDBONOLCK;
            } else if (!strcmp(argv[i], "-nb")) {
                omode |= TDBOLCKNB;
            } else if (!strcmp(argv[i], "-dk")) {
                dmode = -1;
            } else if (!strcmp(argv[i], "-dc")) {
                dmode = 1;
            } else if (!strcmp(argv[i], "-dai")) {
                dmode = 10;
            } else if (!strcmp(argv[i], "-dad")) {
                dmode = 11;
            } else if (!strcmp(argv[i], "-sx")) {
                sx = true;
            } else {
                usage();
            }
        } else if (!path) {
            path = argv[i];
        } else if (!pkey) {
            pkey = argv[i];
        } else {
            tclistpush2(vals, argv[i]);
        }
    }
    if (!path || !pkey) usage();
    TCMAP *cols = tcmapnew();
    char *pkbuf;
    int pksiz;
    if (sx) {
        pkbuf = tchexdecode(pkey, &pksiz);
        for (int i = 0; i < tclistnum(vals) - 1; i += 2) {
            const char *name = tclistval2(vals, i);
            const char *value = tclistval2(vals, i + 1);
            int nsiz;
            char *nbuf = tchexdecode(name, &nsiz);
            int vsiz;
            char *vbuf = tchexdecode(value, &vsiz);
            tcmapput(cols, nbuf, nsiz, vbuf, vsiz);
            tcfree(vbuf);
            tcfree(nbuf);
        }
    } else {
        pksiz = strlen(pkey);
        pkbuf = tcmemdup(pkey, pksiz);
        for (int i = 0; i < tclistnum(vals) - 1; i += 2) {
            const char *name = tclistval2(vals, i);
            const char *value = tclistval2(vals, i + 1);
            tcmapput2(cols, name, value);
        }
    }
    int rv = procput(path, pkbuf, pksiz, cols, omode, dmode);
    tcmapdel(cols);
    tcfree(pkbuf);
    return rv;
}

/* parse arguments of out command */
static int runout(int argc, char **argv) {
    char *path = NULL;
    char *pkey = NULL;
    int omode = 0;
    bool sx = false;
    for (int i = 2; i < argc; i++) {
        if (!path && argv[i][0] == '-') {
            if (!strcmp(argv[i], "-nl")) {
                omode |= TDBONOLCK;
            } else if (!strcmp(argv[i], "-nb")) {
                omode |= TDBOLCKNB;
            } else if (!strcmp(argv[i], "-sx")) {
                sx = true;
            } else {
                usage();
            }
        } else if (!path) {
            path = argv[i];
        } else if (!pkey) {
            pkey = argv[i];
        } else {
            usage();
        }
    }
    if (!path || !pkey) usage();
    int pksiz;
    char *pkbuf;
    if (sx) {
        pkbuf = tchexdecode(pkey, &pksiz);
    } else {
        pksiz = strlen(pkey);
        pkbuf = tcmemdup(pkey, pksiz);
    }
    int rv = procout(path, pkbuf, pksiz, omode);
    tcfree(pkbuf);
    return rv;
}

/* parse arguments of get command */
static int runget(int argc, char **argv) {
    char *path = NULL;
    char *pkey = NULL;
    int omode = 0;
    bool sx = false;
    bool px = false;
    bool pz = false;
    for (int i = 2; i < argc; i++) {
        if (!path && argv[i][0] == '-') {
            if (!strcmp(argv[i], "-nl")) {
                omode |= TDBONOLCK;
            } else if (!strcmp(argv[i], "-nb")) {
                omode |= TDBOLCKNB;
            } else if (!strcmp(argv[i], "-sx")) {
                sx = true;
            } else if (!strcmp(argv[i], "-px")) {
                px = true;
            } else if (!strcmp(argv[i], "-pz")) {
                pz = true;
            } else {
                usage();
            }
        } else if (!path) {
            path = argv[i];
        } else if (!pkey) {
            pkey = argv[i];
        } else {
            usage();
        }
    }
    if (!path || !pkey) usage();
    int pksiz;
    char *pkbuf;
    if (sx) {
        pkbuf = tchexdecode(pkey, &pksiz);
    } else {
        pksiz = strlen(pkey);
        pkbuf = tcmemdup(pkey, pksiz);
    }
    int rv = procget(path, pkbuf, pksiz, omode, px, pz);
    tcfree(pkbuf);
    return rv;
}

/* parse arguments of list command */
static int runlist(int argc, char **argv) {
    char *path = NULL;
    int omode = 0;
    int max = -1;
    bool pv = false;
    bool px = false;
    char *fmstr = NULL;
    for (int i = 2; i < argc; i++) {
        if (!path && argv[i][0] == '-') {
            if (!strcmp(argv[i], "-nl")) {
                omode |= TDBONOLCK;
            } else if (!strcmp(argv[i], "-nb")) {
                omode |= TDBOLCKNB;
            } else if (!strcmp(argv[i], "-m")) {
                if (++i >= argc) usage();
                max = tcatoix(argv[i]);
            } else if (!strcmp(argv[i], "-pv")) {
                pv = true;
            } else if (!strcmp(argv[i], "-px")) {
                px = true;
            } else if (!strcmp(argv[i], "-fm")) {
                if (++i >= argc) usage();
                fmstr = argv[i];
            } else {
                usage();
            }
        } else if (!path) {
            path = argv[i];
        } else {
            usage();
        }
    }
    if (!path) usage();
    int rv = proclist(path, omode, max, pv, px, fmstr);
    return rv;
}

/* parse arguments of search command */
static int runsearch(int argc, char **argv) {
    char *path = NULL;
    TCLIST *conds = tcmpoollistnew(tcmpoolglobal());
    char *oname = NULL;
    char *otype = NULL;
    int omode = 0;
    int max = -1;
    int skip = -1;
    bool pv = false;
    bool px = false;
    bool kw = false;
    bool ph = false;
    int bt = 0;
    bool rm = false;
    char *mtype = NULL;
    for (int i = 2; i < argc; i++) {
        if (!path && argv[i][0] == '-') {
            if (!strcmp(argv[i], "-nl")) {
                omode |= TDBONOLCK;
            } else if (!strcmp(argv[i], "-nb")) {
                omode |= TDBOLCKNB;
            } else if (!strcmp(argv[i], "-ord")) {
                if (++i >= argc) usage();
                oname = argv[i];
                if (++i >= argc) usage();
                otype = argv[i];
            } else if (!strcmp(argv[i], "-m")) {
                if (++i >= argc) usage();
                max = tcatoix(argv[i]);
            } else if (!strcmp(argv[i], "-sk")) {
                if (++i >= argc) usage();
                skip = tcatoix(argv[i]);
            } else if (!strcmp(argv[i], "-kw")) {
                kw = true;
            } else if (!strcmp(argv[i], "-pv")) {
                pv = true;
            } else if (!strcmp(argv[i], "-px")) {
                px = true;
            } else if (!strcmp(argv[i], "-ph")) {
                ph = true;
            } else if (!strcmp(argv[i], "-bt")) {
                if (++i >= argc) usage();
                bt = tcatoix(argv[i]);
            } else if (!strcmp(argv[i], "-rm")) {
                rm = true;
            } else if (!strcmp(argv[i], "-ms")) {
                if (++i >= argc) usage();
                mtype = argv[i];
            } else {
                usage();
            }
        } else if (!path) {
            path = argv[i];
        } else {
            tclistpush2(conds, argv[i]);
        }
    }
    if (!path || tclistnum(conds) % 3 != 0) usage();
    int rv = procsearch(path, conds, oname, otype, omode, max, skip,
            pv, px, kw, ph, bt, rm, mtype);
    return rv;
}

/* parse arguments of optimize command */
static int runoptimize(int argc, char **argv) {
    char *path = NULL;
    char *bstr = NULL;
    char *astr = NULL;
    char *fstr = NULL;
    int opts = UINT8_MAX;
    int omode = 0;
    bool df = false;
    for (int i = 2; i < argc; i++) {
        if (!path && argv[i][0] == '-') {
            if (!strcmp(argv[i], "-tl")) {
                if (opts == UINT8_MAX) opts = 0;
                opts |= TDBTLARGE;
            } else if (!strcmp(argv[i], "-td")) {
                if (opts == UINT8_MAX) opts = 0;
                opts |= TDBTDEFLATE;
            } else if (!strcmp(argv[i], "-tb")) {
                if (opts == UINT8_MAX) opts = 0;
                opts |= TDBTBZIP;
            } else if (!strcmp(argv[i], "-tt")) {
                if (opts == UINT8_MAX) opts = 0;
                opts |= TDBTTCBS;
            } else if (!strcmp(argv[i], "-tx")) {
                if (opts == UINT8_MAX) opts = 0;
                opts |= TDBTEXCODEC;
            } else if (!strcmp(argv[i], "-tz")) {
                if (opts == UINT8_MAX) opts = 0;
            } else if (!strcmp(argv[i], "-nl")) {
                omode |= TDBONOLCK;
            } else if (!strcmp(argv[i], "-nb")) {
                omode |= TDBOLCKNB;
            } else if (!strcmp(argv[i], "-df")) {
                df = true;
            } else {
                usage();
            }
        } else if (!path) {
            path = argv[i];
        } else if (!bstr) {
            bstr = argv[i];
        } else if (!astr) {
            astr = argv[i];
        } else if (!fstr) {
            fstr = argv[i];
        } else {
            usage();
        }
    }
    if (!path) usage();
    int bnum = bstr ? tcatoix(bstr) : -1;
    int apow = astr ? tcatoix(astr) : -1;
    int fpow = fstr ? tcatoix(fstr) : -1;
    int rv = procoptimize(path, bnum, apow, fpow, opts, omode, df);
    return rv;
}

/* parse arguments of setindex command */
static int runsetindex(int argc, char **argv) {
    char *path = NULL;
    char *name = NULL;
    int omode = 0;
    int type = TDBITLEXICAL;
    for (int i = 2; i < argc; i++) {
        if (!path && argv[i][0] == '-') {
            if (!strcmp(argv[i], "-nl")) {
                omode |= TDBONOLCK;
            } else if (!strcmp(argv[i], "-nb")) {
                omode |= TDBOLCKNB;
            } else if (!strcmp(argv[i], "-it")) {
                if (++i >= argc) usage();
                type = tctdbstrtoindextype(argv[i]);
            } else {
                usage();
            }
        } else if (!path) {
            path = argv[i];
        } else if (!name) {
            name = argv[i];
        } else {
            usage();
        }
    }
    if (!path || !name) usage();
    if (type < 0) usage();
    int rv = procsetindex(path, name, omode, type);
    return rv;
}

/* parse arguments of importtsv command */
static int runimporttsv(int argc, char **argv) {
    char *path = NULL;
    char *file = NULL;
    int omode = 0;
    bool sc = false;
    for (int i = 2; i < argc; i++) {
        if (!path && argv[i][0] == '-') {
            if (!strcmp(argv[i], "-nl")) {
                omode |= TDBONOLCK;
            } else if (!strcmp(argv[i], "-nb")) {
                omode |= TDBOLCKNB;
            } else if (!strcmp(argv[i], "-sc")) {
                sc = true;
            } else {
                usage();
            }
        } else if (!path) {
            path = argv[i];
        } else if (!file) {
            file = argv[i];
        } else {
            usage();
        }
    }
    if (!path) usage();
    int rv = procimporttsv(path, file, omode, sc);
    return rv;
}

/* parse arguments of version command */
static int runversion(int argc, char **argv) {
    int rv = procversion();
    return rv;
}

/* perform create command */
static int proccreate(const char *path, int bnum, int apow, int fpow, int opts) {
    TCTDB *tdb = tctdbnew();
    if (!INVALIDHANDLE(g_dbgfd)) tctdbsetdbgfd(tdb, g_dbgfd);
    if (!tctdbsetcodecfunc(tdb, _tc_recencode, NULL, _tc_recdecode, NULL)) printerr(tdb);
    if (!tctdbtune(tdb, bnum, apow, fpow, opts)) {
        printerr(tdb);
        tctdbdel(tdb);
        return 1;
    }
    if (!tctdbopen(tdb, path, TDBOWRITER | TDBOCREAT | TDBOTRUNC)) {
        printerr(tdb);
        tctdbdel(tdb);
        return 1;
    }
    bool err = false;
    if (!tctdbclose(tdb)) {
        printerr(tdb);
        err = true;
    }
    tctdbdel(tdb);
    return err ? 1 : 0;
}

/* perform inform command */
static int procinform(const char *path, int omode) {
    TCTDB *tdb = tctdbnew();
    if (!INVALIDHANDLE(g_dbgfd)) tctdbsetdbgfd(tdb, g_dbgfd);
    tctdbsetcodecfunc(tdb, _tc_recencode, NULL, _tc_recdecode, NULL);
    if (!tctdbopen(tdb, path, TDBOREADER | omode)) {
        printerr(tdb);
        tctdbdel(tdb);
        return 1;
    }
    bool err = false;
    const char *npath = tctdbpath(tdb);
    if (!npath) npath = "(unknown)";
    printf("path: %s\n", npath);
    printf("database type: table\n");
    uint8_t flags = tctdbflags(tdb);
    printf("additional flags:");
    if (flags & TDBFOPEN) printf(" open");
    if (flags & TDBFFATAL) printf(" fatal");
    printf("\n");
    printf("bucket number: %" PRIuMAX "\n", (uint64_t) tctdbbnum(tdb));
#ifndef NDEBUG
    if (tdb->hdb->cnt_writerec >= 0)
        printf("used bucket number: %" PRIdMAX "\n", (int64_t) tctdbbnumused(tdb));
#endif
    printf("alignment: %u\n", tctdbalign(tdb));
    printf("free block pool: %u\n", tctdbfbpmax(tdb));
    printf("index number: %d\n", tctdbinum(tdb));
    TDBIDX *idxs = tdb->idxs;
    int inum = tdb->inum;
    for (int i = 0; i < inum; i++) {
        TDBIDX *idxp = idxs + i;
        switch (idxp->type) {
            case TDBITLEXICAL:
                printf("  name=%s, type=lexical, rnum=%" PRIdMAX ", fsiz=%" PRIdMAX "\n",
                        idxp->name, (int64_t) tcbdbrnum(idxp->db), (int64_t) tcbdbfsiz(idxp->db));
                break;
            case TDBITDECIMAL:
                printf("  name=%s, type=decimal, rnum=%" PRIdMAX ", fsiz=%" PRIdMAX "\n",
                        idxp->name, (int64_t) tcbdbrnum(idxp->db), (int64_t) tcbdbfsiz(idxp->db));
                break;
            case TDBITTOKEN:
                printf("  name=%s, type=token, rnum=%" PRIdMAX ", fsiz=%" PRIdMAX "\n",
                        idxp->name, (int64_t) tcbdbrnum(idxp->db), (int64_t) tcbdbfsiz(idxp->db));
                break;
            case TDBITQGRAM:
                printf("  name=%s, type=qgram, rnum=%" PRIdMAX ", fsiz=%" PRIdMAX "\n",
                        idxp->name, (int64_t) tcbdbrnum(idxp->db), (int64_t) tcbdbfsiz(idxp->db));
                break;
        }
    }
    printf("unique ID seed: %" PRIdMAX "\n", (int64_t) tctdbuidseed(tdb));
    printf("inode number: %" PRIdMAX "\n", (int64_t) tctdbinode(tdb));
    char date[48];
    tcdatestrwww(tctdbmtime(tdb), INT_MAX, date);
    printf("modified time: %s\n", date);
    uint8_t opts = tctdbopts(tdb);
    printf("options:");
    if (opts & TDBTLARGE) printf(" large");
    if (opts & TDBTDEFLATE) printf(" deflate");
    if (opts & TDBTBZIP) printf(" bzip");
    if (opts & TDBTTCBS) printf(" tcbs");
    if (opts & TDBTEXCODEC) printf(" excodec");
    printf("\n");
    printf("record number: %" PRIuMAX "\n", (uint64_t) tctdbrnum(tdb));
    printf("file size: %" PRIuMAX "\n", (uint64_t) tctdbfsiz(tdb));
    if (!tctdbclose(tdb)) {
        if (!err) printerr(tdb);
        err = true;
    }
    tctdbdel(tdb);
    return err ? 1 : 0;
}

/* perform put command */
static int procput(const char *path, const char *pkbuf, int pksiz, TCMAP *cols,
        int omode, int dmode) {
    TCTDB *tdb = tctdbnew();
    if (!INVALIDHANDLE(g_dbgfd)) tctdbsetdbgfd(tdb, g_dbgfd);
    if (!tctdbsetcodecfunc(tdb, _tc_recencode, NULL, _tc_recdecode, NULL)) printerr(tdb);
    if (!tctdbopen(tdb, path, TDBOWRITER | omode)) {
        printerr(tdb);
        tctdbdel(tdb);
        return 1;
    }
    bool err = false;
    char pknumbuf[TCNUMBUFSIZ];
    if (pksiz < 1) {
        pksiz = sprintf(pknumbuf, "%" PRIdMAX "", (int64_t) tctdbgenuid(tdb));
        pkbuf = pknumbuf;
    }
    const char *vbuf;
    switch (dmode) {
        case -1:
            if (!tctdbputkeep(tdb, pkbuf, pksiz, cols)) {
                printerr(tdb);
                err = true;
            }
            break;
        case 1:
            if (!tctdbputcat(tdb, pkbuf, pksiz, cols)) {
                printerr(tdb);
                err = true;
            }
            break;
        case 10:
            vbuf = tcmapget2(cols, "_num");
            if (!vbuf) vbuf = "1";
            if (tctdbaddint(tdb, pkbuf, pksiz, tcatoi(vbuf)) == INT_MIN) {
                printerr(tdb);
                err = true;
            }
            break;
        case 11:
            vbuf = tcmapget2(cols, "_num");
            if (!vbuf) vbuf = "1.0";
            if (isnan(tctdbadddouble(tdb, pkbuf, pksiz, tcatof(vbuf)))) {
                printerr(tdb);
                err = true;
            }
            break;
        default:
            if (!tctdbput(tdb, pkbuf, pksiz, cols)) {
                printerr(tdb);
                err = true;
            }
            break;
    }
    if (!tctdbclose(tdb)) {
        if (!err) printerr(tdb);
        err = true;
    }
    tctdbdel(tdb);
    return err ? 1 : 0;
}

/* perform out command */
static int procout(const char *path, const char *pkbuf, int pksiz, int omode) {
    TCTDB *tdb = tctdbnew();
    if (!INVALIDHANDLE(g_dbgfd)) tctdbsetdbgfd(tdb, g_dbgfd);
    if (!tctdbsetcodecfunc(tdb, _tc_recencode, NULL, _tc_recdecode, NULL)) printerr(tdb);
    if (!tctdbopen(tdb, path, TDBOWRITER | omode)) {
        printerr(tdb);
        tctdbdel(tdb);
        return 1;
    }
    bool err = false;
    if (!tctdbout(tdb, pkbuf, pksiz)) {
        printerr(tdb);
        err = true;
    }
    if (!tctdbclose(tdb)) {
        if (!err) printerr(tdb);
        err = true;
    }
    tctdbdel(tdb);
    return err ? 1 : 0;
}

/* perform get command */
static int procget(const char *path, const char *pkbuf, int pksiz, int omode, bool px, bool pz) {
    TCTDB *tdb = tctdbnew();
    if (!INVALIDHANDLE(g_dbgfd)) tctdbsetdbgfd(tdb, g_dbgfd);
    if (!tctdbsetcodecfunc(tdb, _tc_recencode, NULL, _tc_recdecode, NULL)) printerr(tdb);
    if (!tctdbopen(tdb, path, TDBOREADER | omode)) {
        printerr(tdb);
        tctdbdel(tdb);
        return 1;
    }
    bool err = false;
    TCMAP *cols = tctdbget(tdb, pkbuf, pksiz);
    if (cols) {
        tcmapiterinit(cols);
        const char *kbuf;
        int ksiz;
        while ((kbuf = tcmapiternext(cols, &ksiz)) != NULL) {
            int vsiz;
            const char *vbuf = tcmapiterval(kbuf, &vsiz);
            printdata(kbuf, ksiz, px);
            putchar('\t');
            printdata(vbuf, vsiz, px);
            putchar(pz ? '\t' : '\n');
        }
        tcmapdel(cols);
    } else {
        printerr(tdb);
        err = true;
    }
    if (!tctdbclose(tdb)) {
        if (!err) printerr(tdb);
        err = true;
    }
    tctdbdel(tdb);
    return err ? 1 : 0;
}

/* perform list command */
static int proclist(const char *path, int omode, int max, bool pv, bool px, const char *fmstr) {
    TCTDB *tdb = tctdbnew();
    if (!INVALIDHANDLE(g_dbgfd)) tctdbsetdbgfd(tdb, g_dbgfd);
    if (!tctdbsetcodecfunc(tdb, _tc_recencode, NULL, _tc_recdecode, NULL)) printerr(tdb);
    if (!tctdbopen(tdb, path, TDBOREADER | omode)) {
        printerr(tdb);
        tctdbdel(tdb);
        return 1;
    }
    bool err = false;
    if (fmstr) {
        TCLIST *pkeys = tctdbfwmkeys2(tdb, fmstr, max);
        for (int i = 0; i < tclistnum(pkeys); i++) {
            int pksiz;
            const char *pkbuf = tclistval(pkeys, i, &pksiz);
            printdata(pkbuf, pksiz, px);
            if (pv) {
                TCMAP *cols = tctdbget(tdb, pkbuf, pksiz);
                if (cols) {
                    tcmapiterinit(cols);
                    const char *kbuf;
                    int ksiz;
                    while ((kbuf = tcmapiternext(cols, &ksiz)) != NULL) {
                        int vsiz;
                        const char *vbuf = tcmapiterval(kbuf, &vsiz);
                        putchar('\t');
                        printdata(kbuf, ksiz, px);
                        putchar('\t');
                        printdata(vbuf, vsiz, px);
                    }
                    tcmapdel(cols);
                }
            }
            putchar('\n');
        }
        tclistdel(pkeys);
    } else {
        if (!tctdbiterinit(tdb)) {
            printerr(tdb);
            err = true;
        }
        int cnt = 0;
        TCMAP *cols;
        while ((cols = tctdbiternext3(tdb)) != NULL) {
            int pksiz;
            const char *pkbuf = tcmapget(cols, "", 0, &pksiz);
            if (pkbuf) {
                printdata(pkbuf, pksiz, px);
                if (pv) {
                    tcmapiterinit(cols);
                    const char *kbuf;
                    int ksiz;
                    while ((kbuf = tcmapiternext(cols, &ksiz)) != NULL) {
                        if (*kbuf == '\0') continue;
                        int vsiz;
                        const char *vbuf = tcmapiterval(kbuf, &vsiz);
                        putchar('\t');
                        printdata(kbuf, ksiz, px);
                        putchar('\t');
                        printdata(vbuf, vsiz, px);
                    }
                }
            }
            tcmapdel(cols);
            putchar('\n');
            if (max >= 0 && ++cnt >= max) break;
        }
    }
    if (!tctdbclose(tdb)) {
        if (!err) printerr(tdb);
        err = true;
    }
    tctdbdel(tdb);
    return err ? 1 : 0;
}

/* perform search command */
static int procsearch(const char *path, TCLIST *conds, const char *oname, const char *otype,
        int omode, int max, int skip, bool pv, bool px, bool kw, bool ph, int bt,
        bool rm, const char *mtype) {
    TCTDB *tdb = tctdbnew();
    if (!INVALIDHANDLE(g_dbgfd)) tctdbsetdbgfd(tdb, g_dbgfd);
    if (!tctdbsetcodecfunc(tdb, _tc_recencode, NULL, _tc_recdecode, NULL)) printerr(tdb);
    if (!tctdbopen(tdb, path, (rm ? TDBOWRITER : TDBOREADER) | omode)) {
        printerr(tdb);
        tctdbdel(tdb);
        return 1;
    }
    bool err = false;
    TDBQRY *qry = tctdbqrynew(tdb);
    int cnum = tclistnum(conds);
    for (int i = 0; i < cnum - 2; i += 3) {
        const char *name = tclistval2(conds, i);
        const char *opstr = tclistval2(conds, i + 1);
        const char *expr = tclistval2(conds, i + 2);
        int op = tctdbqrystrtocondop(opstr);
        if (op >= 0) tctdbqryaddcond(qry, name, op, expr);
    }
    if (oname) {
        int type = tctdbqrystrtoordertype(otype);
        if (type >= 0) tctdbqrysetorder(qry, oname, type);
    }
    tctdbqrysetlimit(qry, max, skip);
    if (rm) {
        double stime = tctime();
        if (!tctdbqrysearchout(qry)) {
            printerr(tdb);
            err = true;
        }
        double etime = tctime();
        if (ph) {
            TCLIST *hints = tcstrsplit(tctdbqryhint(qry), "\n");
            int hnum = tclistnum(hints);
            for (int i = 0; i < hnum; i++) {
                const char *hint = tclistval2(hints, i);
                if (*hint == '\0') continue;
                printf("\t:::: %s\n", hint);
            }
            tclistdel(hints);
            printf("\t:::: number of records: %d\n", tctdbqrycount(qry));
            printf("\t:::: elapsed time: %.5f\n", etime - stime);
        }
    } else if (bt > 0) {
        double sum = 0;
        for (int i = 1; i <= bt; i++) {
            double stime = tctime();
            TCLIST *res = tctdbqrysearch(qry);
            double etime = tctime();
            tclistdel(res);
            printf("%d: %.5f sec.\n", i, etime - stime);
            sum += etime - stime;
        }
        printf("----\n");
        printf("total: %.5f sec. (%.5f s/q = %.5f q/s)\n", sum, sum / bt, bt / sum);
    } else {
        double stime = tctime();
        TCLIST *res;
        TCLIST *hints;
        int count;
        int mtnum = mtype ? tctdbmetastrtosettype(mtype) : -1;
        if (mtnum >= 0) {
            TDBQRY * qrys[cnum / 3 + 1];
            int qnum = 0;
            for (int i = 0; i < cnum - 2; i += 3) {
                const char *name = tclistval2(conds, i);
                const char *opstr = tclistval2(conds, i + 1);
                const char *expr = tclistval2(conds, i + 2);
                int op = tctdbqrystrtocondop(opstr);
                if (op >= 0) {
                    qrys[qnum] = tctdbqrynew(tdb);
                    tctdbqryaddcond(qrys[qnum], name, op, expr);
                    if (oname) {
                        int type = tctdbqrystrtoordertype(otype);
                        if (type >= 0) tctdbqrysetorder(qrys[qnum], oname, type);
                    }
                    tctdbqrysetlimit(qrys[qnum], max, skip);
                    qnum++;
                }
            }
            res = tctdbmetasearch(qrys, qnum, mtnum);
            hints = qnum > 0 ? tcstrsplit(tctdbqryhint(qrys[0]), "\n") : tclistnew2(1);
            count = qnum > 0 ? tctdbqrycount(qrys[0]) : 0;
            for (int i = 0; i < qnum; i++) {
                tctdbqrydel(qrys[i]);
            }
        } else {
            res = tctdbqrysearch(qry);
            hints = tcstrsplit(tctdbqryhint(qry), "\n");
            count = tctdbqrycount(qry);
        }
        double etime = tctime();
        if (max < 0) max = INT_MAX;
        int rnum = tclistnum(res);
        for (int i = 0; i < rnum && max > 0; i++) {
            int pksiz;
            const char *pkbuf = tclistval(res, i, &pksiz);
            if (kw) {
                TCMAP *cols = tctdbget(tdb, pkbuf, pksiz);
                if (cols) {
                    TCLIST *texts = tctdbqrykwic(qry, cols, NULL, 16, TCKWMUTAB);
                    int tnum = tclistnum(texts);
                    for (int j = 0; j < tnum && max > 0; j++) {
                        int tsiz;
                        const char *text = tclistval(texts, j, &tsiz);
                        printdata(pkbuf, pksiz, px);
                        putchar('\t');
                        printdata(text, tsiz, px);
                        putchar('\n');
                        max--;
                    }
                    tclistdel(texts);
                    tcmapdel(cols);
                }
            } else {
                printdata(pkbuf, pksiz, px);
                if (pv) {
                    TCMAP *cols = tctdbget(tdb, pkbuf, pksiz);
                    if (cols) {
                        tcmapiterinit(cols);
                        const char *kbuf;
                        int ksiz;
                        while ((kbuf = tcmapiternext(cols, &ksiz)) != NULL) {
                            int vsiz;
                            const char *vbuf = tcmapiterval(kbuf, &vsiz);
                            putchar('\t');
                            printdata(kbuf, ksiz, px);
                            putchar('\t');
                            printdata(vbuf, vsiz, px);
                        }
                        tcmapdel(cols);
                    }
                }
                putchar('\n');
                max--;
            }
        }
        if (ph) {
            int hnum = tclistnum(hints);
            for (int i = 0; i < hnum; i++) {
                const char *hint = tclistval2(hints, i);
                if (*hint == '\0') continue;
                printf("\t:::: %s\n", hint);
            }
            printf("\t:::: number of records: %d\n", count);
            printf("\t:::: elapsed time: %.5f\n", etime - stime);
        }
        tclistdel(hints);
        tclistdel(res);
    }
    tctdbqrydel(qry);
    if (!tctdbclose(tdb)) {
        if (!err) printerr(tdb);
        err = true;
    }
    tctdbdel(tdb);
    return err ? 1 : 0;
}

/* perform optimize command */
static int procoptimize(const char *path, int bnum, int apow, int fpow, int opts, int omode,
        bool df) {
    TCTDB *tdb = tctdbnew();
    if (!INVALIDHANDLE(g_dbgfd)) tctdbsetdbgfd(tdb, g_dbgfd);
    if (!tctdbsetcodecfunc(tdb, _tc_recencode, NULL, _tc_recdecode, NULL)) printerr(tdb);
    int64_t msiz = 0;
    TCMAP *info = tcsysinfo();
    if (info) {
        msiz = tcatoi(tcmapget4(info, "total", "0"));
        tcmapdel(info);
    }
    if (!tctdbsetinvcache(tdb, msiz >= (1 << 30) ? msiz / 4 : 0, 1.0)) printerr(tdb);
    if (!tctdbopen(tdb, path, TDBOWRITER | omode)) {
        printerr(tdb);
        tctdbdel(tdb);
        return 1;
    }
    bool err = false;
    if (df) {
        if (!tctdbdefrag(tdb, INT64_MAX)) {
            printerr(tdb);
            err = true;
        }
    } else {
        if (!tctdboptimize(tdb, bnum, apow, fpow, opts)) {
            printerr(tdb);
            err = true;
        }
    }
    if (!tctdbclose(tdb)) {
        if (!err) printerr(tdb);
        err = true;
    }
    tctdbdel(tdb);
    return err ? 1 : 0;
}

/* perform setindex command */
static int procsetindex(const char *path, const char *name, int omode, int type) {
    TCTDB *tdb = tctdbnew();
    if (!INVALIDHANDLE(g_dbgfd)) tctdbsetdbgfd(tdb, g_dbgfd);
    if (!tctdbsetcodecfunc(tdb, _tc_recencode, NULL, _tc_recdecode, NULL)) printerr(tdb);
    int64_t msiz = 0;
    TCMAP *info = tcsysinfo();
    if (info) {
        msiz = tcatoi(tcmapget4(info, "total", "0"));
        tcmapdel(info);
    }
    if (!tctdbsetinvcache(tdb, msiz >= (1 << 30) ? msiz / 4 : 0, 1.0)) printerr(tdb);
    if (!tctdbopen(tdb, path, TDBOWRITER | omode)) {
        printerr(tdb);
        tctdbdel(tdb);
        return 1;
    }
    bool err = false;
    if (!tctdbsetindex(tdb, name, type)) {
        printerr(tdb);
        err = true;
    }
    if (!tctdbclose(tdb)) {
        if (!err) printerr(tdb);
        err = true;
    }
    tctdbdel(tdb);
    return err ? 1 : 0;
}

/* perform importtsv command */
static int procimporttsv(const char *path, const char *file, int omode, bool sc) {
    FILE *ifp = file ? fopen(file, "rb") : stdin;
    if (!ifp) {
        fprintf(stderr, "%s: could not open\n", file ? file : "(stdin)");
        return 1;
    }
    TCTDB *tdb = tctdbnew();
    if (!INVALIDHANDLE(g_dbgfd)) tctdbsetdbgfd(tdb, g_dbgfd);
    if (!tctdbsetcodecfunc(tdb, _tc_recencode, NULL, _tc_recdecode, NULL)) printerr(tdb);
    int64_t msiz = 0;
    TCMAP *info = tcsysinfo();
    if (info) {
        msiz = tcatoi(tcmapget4(info, "total", "0"));
        tcmapdel(info);
    }
    if (!tctdbsetinvcache(tdb, msiz >= (1 << 30) ? msiz / 4 : 0, 1.0)) printerr(tdb);
    if (!tctdbopen(tdb, path, TDBOWRITER | TDBOCREAT | omode)) {
        printerr(tdb);
        tctdbdel(tdb);
        if (ifp != stdin) fclose(ifp);
        return 1;
    }
    bool err = false;
    char *line, numbuf[TCNUMBUFSIZ];
    int cnt = 0;
    while (!err && (line = mygetline(ifp)) != NULL) {
        char *pv = strchr(line, '\t');
        if (!pv) {
            tcfree(line);
            continue;
        }
        *pv = '\0';
        if (sc) tcstrutfnorm(line, TCUNSPACE | TCUNLOWER | TCUNNOACC | TCUNWIDTH);
        const char *pkey;
        if (*line == '\0') {
            sprintf(numbuf, "%" PRIdMAX "", (int64_t) tctdbgenuid(tdb));
            pkey = numbuf;
        } else {
            pkey = line;
        }
        if (!tctdbput3(tdb, pkey, pv + 1)) {
            printerr(tdb);
            err = true;
        }
        tcfree(line);
        if (cnt > 0 && cnt % 100 == 0) {
            putchar('.');
            fflush(stdout);
            if (cnt % 5000 == 0) printf(" (%08d)\n", cnt);
        }
        cnt++;
    }
    printf(" (%08d)\n", cnt);
    if (!tctdbclose(tdb)) {
        if (!err) printerr(tdb);
        err = true;
    }
    tctdbdel(tdb);
    if (ifp != stdin) fclose(ifp);
    return err ? 1 : 0;
}

/* perform version command */
static int procversion(void) {
    printf("Tokyo Cabinet version %s (%d:%s) for %s\n",
            tcversion, _TC_LIBVER, _TC_FORMATVER, TCSYSNAME);
    printf("Copyright (C) 2006-2012 FAL Labs\n");
    return 0;
}



// END OF FILE
